package com.openx.cloud.server.autoconfigure.web;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.openx.boot.core.converter.DateConverter;
import com.openx.boot.core.exception.BaseAccessDeniedHandler;
import com.openx.boot.core.exception.BaseAuthenticationEntryPoint;
import com.openx.boot.core.exception.DefaultWebExceptionAdvice;
import com.openx.boot.core.filter.BaseModifyRequestFilter;
import com.openx.boot.core.jackson.JacksonSerializerFeature;
import com.openx.boot.core.jackson.JacksonSerializerFeatureCompatible;
import com.openx.boot.core.utils.PDFUtil;
import com.openx.cloud.server.autoconfigure.security.BearerTokenFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.spring5.SpringTemplateEngine;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

/**
 * @author liuyadu
 */
@Slf4j
@ConditionalOnWebApplication
@Configuration
@EnableConfigurationProperties(WebProperties.class)
public class WebMvcConfiguration implements WebMvcConfigurer {

    /**
     * 统一异常处理配置
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(DefaultWebExceptionAdvice.class)
    public DefaultWebExceptionAdvice globalExceptionHandler() {
        DefaultWebExceptionAdvice exceptionHandler = new DefaultWebExceptionAdvice();
        log.info("默认Web异常处理器 [{}]", exceptionHandler);
        return exceptionHandler;
    }

    /**
     * 支持body缓存
     * 修改请求头等
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean baseModifyRequestFilter() {
        BaseModifyRequestFilter filter = new BaseModifyRequestFilter();
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(filter);
        filterRegistrationBean.setOrder(1);
        log.info("请求装饰过滤器 [{}]", filter);
        return filterRegistrationBean;
    }

    /**
     * 认证过滤器
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public FilterRegistrationBean bearerTokenFilter(RedisConnectionFactory redisConnectionFactory) {
        BearerTokenFilter filter = new BearerTokenFilter(new RedisTokenStore(redisConnectionFactory));
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(filter);
        filterRegistrationBean.setOrder(SecurityWebFiltersOrder.AUTHENTICATION.getOrder() + 2);
        log.info("Bearer token 过滤器[{}]", filter);
        return filterRegistrationBean;
    }

    /**
     * 默认安全配置
     * 多个WebSecurityConfigurerAdapter
     */
    @Configuration
    @Order(101)
    static class DefaultWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers(
                    "/error",
                    "/static/**",
                    "/v2/api-docs/**",
                    "/swagger-resources/**",
                    "/webjars/**",
                    "/favicon.ico");
        }

        /**
         * 默认安全配置
         *
         * @param http
         * @throws Exception
         */
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    //为了统一异常处理。每个资源服务器都应该加上。
                    .exceptionHandling()
                    .accessDeniedHandler(new BaseAccessDeniedHandler())
                    .authenticationEntryPoint(new BaseAuthenticationEntryPoint())
                    .and()
                    .csrf().disable();
        }
    }

    /**
     * 默认objectMapper配置
     *
     * @return
     */
    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        // 排序key
        objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, false);
        //关闭日期序列化为时间戳的功能
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        //关闭序列化的时候没有为属性找到getter方法,报错
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        //关闭反序列化的时候，没有找到属性的setter报错
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        //反序列化的时候如果多了其他属性,不抛出异常
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        //如果是空对象的时候,不抛异常
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        log.info("ObjectMapper [{}]", objectMapper);
        return objectMapper;
    }

    /**
     * 空值处理
     * 只在开放型Api服务中开启。
     * 服务提供者禁止开启,否则feign解码null值将被处理为默认值。
     *
     * @param objectMapper
     * @return
     */
    @Bean
    @ConditionalOnProperty(value = "api.null-value-convert", matchIfMissing = false)
    public HttpMessageConverters httpMessageConverters(ObjectMapper objectMapper) {
        ObjectMapper copy = objectMapper.copy();
        MappingJackson2HttpMessageConverter httpMessageConverter = new MappingJackson2HttpMessageConverter();
        /**
         * 序列换成json时,将所有的long变成string
         * 因为js中得数字类型不能包含所有的java long值
         */
        // json空值处理
        JacksonSerializerFeature[] features = new JacksonSerializerFeature[]{
                JacksonSerializerFeature.WriteNullListAsEmpty,
                JacksonSerializerFeature.WriteNullStringAsEmpty,
                JacksonSerializerFeature.WriteNullBooleanAsFalse,
                JacksonSerializerFeature.WriteNullMapAsEmpty
        };
        copy.setSerializerFactory(copy.getSerializerFactory().withSerializerModifier(new JacksonSerializerFeatureCompatible(features)));
        httpMessageConverter.setObjectMapper(copy);
        log.warn("开启响应数据空值处理。只在开放Api服务中开启,微服务服务提供者禁止开启,否则feign传值将被处理默认值");
        return new HttpMessageConverters(httpMessageConverter);
    }


    /**
     * 资源处理器
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html", "doc.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    /**
     * 自定义日期转换器
     *
     * @param registry
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new DateConverter());
    }


    @Configuration
    @AutoConfigureAfter({ThymeleafAutoConfiguration.class})
    static class TemplateConfiguration {
        @Bean
        public PDFUtil pdfUtil(SpringTemplateEngine springTemplateEngine) {
            PDFUtil pdfUtil = new PDFUtil(springTemplateEngine);
            log.info("pdfUtil工具类 [{}]", pdfUtil);
            return pdfUtil;
        }
    }


}
